//-------------------------------------------------------------------
//	Copyright (c) 2013-2014  Zhirnov Andrey
//	This file is part of the "UX OpwnGL Framework" project.
//	See copyright notice in "UX_GL_Framework.h".
//-------------------------------------------------------------------

#pragma once

#include "VoxelTerrain.h"
#include "CLComputePass.h"
#include "VoxelsBlockToMesh.h"

namespace Voxels
{

	//
	// Voxel Terrain Simulation
	//

	template <typename TVoxel, uint VoxelID>
	class VoxelTerrainSimulation
	{
	// types
	public:
		typedef VoxelTerrain< TVoxel, VoxelID >				VoxelTerrain_t;
		typedef CLComputePass< TVoxel, VoxelID >			ComputePass_t;
		typedef typename ComputePass_t::IEventListener		IEventListener;
		typedef typename ComputePass_t::IEventListenerPtr	IEventListenerPtr;
		typedef VoxelTerrainSimulation< TVoxel, VoxelID >	Self;
		typedef TVoxel										Voxel_t;

		enum {
			MAX_ITERATIONS	= uint(-1),
			VOXEL_TYPE_ID	= VoxelID,
		};
		

		//
		// Simulation Event Listener
		//
		class ISimulationEventListener : public IEventListener
		{
		public:
			virtual void HeightMapUpdated (const GLTexture2DPtr &) = 0;
		};

		SHARED_POINTER( ISimulationEventListener );


	private:
		typedef function< void >									_Message_t;
		typedef TMultiThreadQueue< circular_queue< _Message_t > >	_MessageQueue_t;
		typedef static_array< GLTexture2DPtr, 2 >					_GLHeightMaps_t;


		//
		// Event Listener Wrapper
		//
		class _EventListenerWrapper : public IEventListener
		{
		private:
			typedef static_array< CLImage, _GLHeightMaps_t::STATIC_COUNT, TSafeCopyStrategy<CLImage> >	_CLHeightMaps_t;

		private:
			ISimulationEventListenerPtr	_eventListener;
			Self *						_voxelTerrainSimulation;

			_GLHeightMaps_t				_glHeightMaps;
			_CLHeightMaps_t				_clHeightMaps;

			uvec3						_blockSize;
			uvec3						_numBlocks;
			uint						_currentTexture;

			CLProgramPtr				_progBlockToHeight;
			CLKernel					_kernBlockToHeight;

			CLProgramPtr				_progInitHeight;
			CLKernel					_kernInitHeight;

			bool						_genHeightMap;

		public:
			// main thread //
			_EventListenerWrapper (Self *vts) :
				_genHeightMap(false), _currentTexture(0), _voxelTerrainSimulation(vts)
			{}

			~_EventListenerWrapper ()
			{
			}
			
			void SetHeightMaps (const _GLHeightMaps_t &heightMaps);
			void SetBlockSize (const uvec3 &blockSize)						{ _blockSize = blockSize; }
			void SetNumBlocks (const uvec3 &numBlocks)						{ _numBlocks = numBlocks; }

			// simulation thread //
			void SetEventListener (const ISimulationEventListenerPtr &el)	{ _eventListener = el; }

			void Init (uni_c_string options);
			
			void OnBeginProcessing ();
			void OnEndProcessing ();

			// IEventListener //
			bool OnCreate ()														override;
			void BeforeRunProgram (CLQueue *, CLKernel *, const ivec3 &)			override;
			void AfterRunProgram (CLQueue *, CLImage *, CLImage *, const ivec3 &)	override;

		private:
			void _InitHeightMap (CLQueue *);
			void _GenerateHeightMap (CLQueue *, CLImage *, const ivec3 &);
			void _HeightMapUpdated (const GLTexture2DPtr &);
			void _OnHeightMapUpdated ();
		};
		
		SHARED_POINTER( _EventListenerWrapper );


		//
		// Message Processor
		//
		struct _MessageProcessor
		{
			void operator () (const _Message_t &msg)
			{
				msg.Call();
			}
		};


		//
		// Thread
		//
		struct _Thread
		{
		private:
			Thread				thread;
			_MessageQueue_t		messageQueue;
			atomic_bool			looping;
		
		public:
			_Thread () : looping(false) {}
			~_Thread ();

			bool Run ();
			void Quit ();

			void Push (const _Message_t &msg);

		private:
			void _Loop ();
			static void _ThreadProc (void *);
		};

		enum EThreadType {
			MAIN_THREAD,
			SIMULATION_THREAD,
		};


	// variables
	private:
		_EventListenerWrapperPtr	_eventListener;

		// simulation thread //
		_Thread				_simulationThread;
		VoxelTerrain_t		_terrain;
		ComputePass_t		_computePass;
		string				_clOptions;
		uint				_iteration;
		atomic_bool			_inProcess;
		atomic_bool			_simulationPaused;

		// triangulation thread //
		_Thread				_triangulationThread;
		binary_array_t		_tmpVertices;
		binary_array_t		_tmpIndices;

		// main thread //
		_MessageQueue_t		_mainMessageQueue;



	// methods
	public:
		VoxelTerrainSimulation ();
		~VoxelTerrainSimulation ();

		bool Create (uni_c_string filePath, const uvec3 &blockSize, const uvec3 &numBlocks);
		void Destroy ();

		void StartSimulation (uni_c_string program, uint flags = ComputePass_t::READ_WRITE,
							  uint iterations = MAX_ITERATIONS, const ISimulationEventListenerPtr &eventListener = null,
							  const uni_array<const GLTexture2DPtr> &heightMaps = uni_array<const GLTexture2DPtr>());

		void StopSimulation ();

		void PauseSimulation ();
		void ResumeSimulation ();

		void Update ();

		bool IsSimulationPaused ()		const		{ return _simulationPaused; }
		bool IsSimulationInProcess ()	const		{ return _inProcess; }


	private:
		void _Simulate ();
		void _Triangulate (VoxelsBlock *block);

		void _Create (const string &filePath, const uvec3 &blockSize, const uvec3 &numBlocks);
		void _StartSimulation (const string &program, uint flags, uint iterations,
								const ISimulationEventListenerPtr &eventListener, const _GLHeightMaps_t &heightMaps);

		void _StopSimulation ();

		void _HeightMapUpdated ();

		void _Push (EThreadType type, const _Message_t &msg);
	};


	
/*
=================================================
	constructor
=================================================
*/
	template <typename V, uint ID>
	inline VoxelTerrainSimulation<V,ID>::VoxelTerrainSimulation () :
		_iteration(0), _inProcess(false), _simulationPaused(false)
	{
		_eventListener = new _EventListenerWrapper( this );
	}
	
/*
=================================================
	destructor
=================================================
*/
	template <typename V, uint ID>
	inline VoxelTerrainSimulation<V,ID>::~VoxelTerrainSimulation ()
	{
	}

/*
=================================================
	Create
=================================================
*/
	template <typename V, uint ID>
	inline bool VoxelTerrainSimulation<V,ID>::Create (uni_c_string filePath, const uvec3 &blockSize, const uvec3 &numBlocks)
	{
		CHECK_RETURN( _simulationThread.Run() );
		_Push( SIMULATION_THREAD, FunctionBuilder::Create( this, &Self::_Create, filePath, blockSize, numBlocks ) );
		return true;
	}
	
/*
=================================================
	Destroy
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::Destroy ()
	{
		StopSimulation();
		Update();

		_simulationThread.Quit();
		_triangulationThread.Quit();
		
		_eventListener = null;
	}

/*
=================================================
	StartSimulation
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::StartSimulation (uni_c_string program, uint flags, uint iterations,
																const ISimulationEventListenerPtr &eventListener,
																const uni_array<const GLTexture2DPtr> &heightMaps)
	{
		const _GLHeightMaps_t	height_maps( heightMaps );
		_Push( SIMULATION_THREAD, FunctionBuilder::Create( this, &Self::_StartSimulation, program, flags, iterations, eventListener, height_maps ) );
	}
	
/*
=================================================
	StopSimulation
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::StopSimulation ()
	{
		_Push( SIMULATION_THREAD,FunctionBuilder::Create( this, &Self::_StopSimulation ) );
	}
	
/*
=================================================
	StopSimulation
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::PauseSimulation ()
	{
		_simulationPaused = true;
	}
	
/*
=================================================
	StopSimulation
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::ResumeSimulation ()
	{
		_simulationPaused = false;
		_Push( SIMULATION_THREAD,FunctionBuilder::Create( this, &Self::_Simulate ) );
	}
	
/*
=================================================
	Update
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::Update ()
	{
		_MessageProcessor processor;

		_mainMessageQueue.Flush();
		_mainMessageQueue.ProcessAll( processor );
	}

/*
=================================================
	_Triangulate
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_Triangulate (VoxelsBlock *block)
	{
		CHECK( VoxelsTriangulation::ToMesh( op, *block, _tmpVertices, _tmpIndices ) );
	}
	
/*
=================================================
	_Simulate
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_Simulate ()
	{
		if ( _simulationPaused )
			return;

		if ( _iteration == 0 )
			return;

		--_iteration;

		_eventListener->OnBeginProcessing();

		_computePass.SetIteration( _iteration );
		CHECK( _terrain.ProcessBlocks( _computePass ) );

		_eventListener->OnEndProcessing();

		_inProcess = ( _iteration > 0 );
		
		if ( _inProcess )
			_Push( SIMULATION_THREAD, FunctionBuilder::Create( this, &Self::_Simulate ) );
	}

/*
=================================================
	_Create
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_Create (const string &filePath, const uvec3 &blockSize, const uvec3 &numBlocks)
	{
		if ( _terrain.IsExist( filePath ) ) {
			CHECK_RETURN_R( _terrain.Open( filePath ), );
		} else {
			CHECK_RETURN_R( _terrain.Create( filePath, blockSize, numBlocks ), );
		}

		_eventListener->SetBlockSize( _terrain.BlockSize() );
		_eventListener->SetNumBlocks( _terrain.NumBlocks() );

		_terrain.SetVoxelInBorder( Voxel_t::GetBorderVoxelZPositive(), Voxel_t::GetBorderVoxelZNegative(), Voxel_t::GetBorderVoxelXY() );

		_clOptions.ClearMem();
		_clOptions <<
			"#define VOXEL_TEXTURE_X_FRAMES_POT		" << _terrain.TextureXFramesPOT() << "\n"
			"#define VOXEL_TEXTURE_BORDER			" << _terrain.BorderSize() << "\n"
			"#define VOXEL_TEXTURE_BLOCK_SIZE_X		" << _terrain.BlockSize().x << "\n"
			"#define VOXEL_TEXTURE_BLOCK_SIZE_Y		" << _terrain.BlockSize().y << "\n"
			"#define VOXEL_TEXTURE_BLOCK_SIZE_Z		" << _terrain.BlockSize().z << "\n"
			"#define PIXEL_PER_VOXEL				" << _terrain.PixelPerVoxel() << "\n"
			"#define VOXEL_TEXTURE_DATA_TYPE		" << _terrain.TexDataTypeName() << "\n";
	}
	
/*
=================================================
	_StartSimulation
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_StartSimulation (const string &program, uint flags, uint iterations,
																const ISimulationEventListenerPtr &eventListener,
																const _GLHeightMaps_t &heightMaps)
	{
		_StopSimulation();
		
		_eventListener->SetEventListener( eventListener );
		_eventListener->SetHeightMaps( heightMaps );

		CHECK_RETURN_R( _computePass.Create( program, flags, _clOptions, _eventListener ), );
		
		_eventListener->Init( _clOptions );

		_iteration	= iterations;
		_inProcess	= iterations > 0;
		
		DEBUG_CONSOLE( (string("Start Simulation, prog: \"") << program << "\", updates: " << iterations).cstr() );

		_Push( SIMULATION_THREAD, FunctionBuilder::Create( this, &Self::_Simulate ) );
	}
	
/*
=================================================
	_StopSimulation
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_StopSimulation ()
	{
		if ( _inProcess )
		{
			DEBUG_CONSOLE( (string("Stop Simulation, iteration: ") << _iteration).cstr() );
		}

		_eventListener->SetEventListener( null );

		_computePass.Destroy();
		_iteration	= 0;
		_inProcess	= false;
	}
	
/*
=================================================
	_StopSimulation
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_Push (EThreadType type, const _Message_t &msg)
	{
		switch ( type )
		{
			default :
				WARNING( "unknown thread type" );

			case SIMULATION_THREAD :
				//_simulationThread.Push( msg );
				//break;

			case MAIN_THREAD :
				_mainMessageQueue.Push( msg );
				break;
		}
	}



//-------------------------- _EventListenerWrapper --------------------------//
/*
=================================================
	OnCreate
=================================================
*/
	template <typename V, uint ID>
	inline bool VoxelTerrainSimulation<V,ID>::_EventListenerWrapper::OnCreate ()
	{
		return _eventListener.IsNotNull() ? _eventListener->OnCreate() : true;
	}
	
/*
=================================================
	BeforeRunProgram
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_EventListenerWrapper::BeforeRunProgram (CLQueue *q, CLKernel *k, const ivec3 &blockCoord)
	{
		if ( _eventListener.IsNotNull() )
			_eventListener->BeforeRunProgram( q, k, blockCoord );
	}
	
/*
=================================================
	AfterRunProgram
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_EventListenerWrapper::AfterRunProgram (CLQueue *q, CLImage *inImg,
																					CLImage *outImg, const ivec3 &blockCoord)
	{
		if ( _eventListener.IsNotNull() )
		{
			if ( _genHeightMap and inImg != null )
				_GenerateHeightMap( q, inImg, blockCoord );

			_eventListener->AfterRunProgram( q, inImg, outImg, blockCoord );
		}
	}
	
/*
=================================================
	SetHeightMaps
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_EventListenerWrapper::SetHeightMaps (const _GLHeightMaps_t &heightMaps)
	{
		_genHeightMap = true;
		_currentTexture = 0;

		FOR( i, _glHeightMaps )
		{
			if ( heightMaps[i].IsNotNull() )
			{
				_glHeightMaps[i] = heightMaps[i];

				CHECK( _clHeightMaps[i].CreateFromTexture( _glHeightMaps[i].ptr(), cl_memory::WRITE_ONLY ) );
			}
			else
			{
				_clHeightMaps[i].Destroy();
				_glHeightMaps[i] = null;

				_genHeightMap = false;
			}
		}
	}
	
/*
=================================================
	_InitHeightMap
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_EventListenerWrapper::_InitHeightMap (CLQueue *q)
	{
		FUNC_PROFILER();

		enum {
			HEIGHT_MAP_BORDER = 2,
		};
		
		CHECK( _progInitHeight.IsNotNull() );
		
		CLImage *	out_image	= &_clHeightMaps[ _currentTexture ];
		cl_mem		cl_mem_obj	= out_image->Id();

		const svec3		height_map_size = svec3( (_blockSize * _numBlocks + HEIGHT_MAP_BORDER*2).To<svec2>(), 1 );
		
		_kernInitHeight.SetArg( 0, out_image->Id() );
		
		GL_CALL( glFinish() );
		CHECK( q->EnqueueAcquireGLObjects( clmem_uni_array( &cl_mem_obj, 1 ) ) );

		CHECK( q->EnqueueNDRange( &_kernInitHeight, svec3(0), height_map_size, svec3(1) ) );
		
		CHECK( q->EnqueueReleaseGLObjects( clmem_uni_array( &cl_mem_obj, 1 ) ) );

		CHECK( q->Finish() );
	}

/*
=================================================
	_GenerateHeightMap
----
	kernel args:
		read_only image2d_t, write_only image2d_t, int3 blockCoord
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_EventListenerWrapper::_GenerateHeightMap (CLQueue *q, CLImage *inImage, const ivec3 &blockCoord)
	{
		FUNC_PROFILER();

		CHECK( _progBlockToHeight.IsNotNull() );

		CLImage *	out_image	= &_clHeightMaps[ _currentTexture ];
		cl_mem		cl_mem_obj	= out_image->Id();

		_kernBlockToHeight.SetArg( 0, inImage->Id() );
		_kernBlockToHeight.SetArg( 1, out_image->Id() );
		_kernBlockToHeight.SetArg( 2, ivec4(blockCoord) );
		
		GL_CALL( glFinish() );
		CHECK( q->EnqueueAcquireGLObjects( clmem_uni_array( &cl_mem_obj, 1 ) ) );

		CHECK( q->EnqueueNDRange( &_kernBlockToHeight, svec3(0), svec3(_blockSize.x, _blockSize.y, 1), svec3(1) ) );
		
		CHECK( q->EnqueueReleaseGLObjects( clmem_uni_array( &cl_mem_obj, 1 ) ) );

		CHECK( q->Finish() );
	}
	
/*
=================================================
	_HeightMapUpdated
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_EventListenerWrapper::_HeightMapUpdated (const GLTexture2DPtr &tex)
	{
		if ( _eventListener.IsNotNull() )
		{
			_eventListener->HeightMapUpdated( tex );
			
			_voxelTerrainSimulation->_Push( SIMULATION_THREAD, 
				FunctionBuilder::Create( _EventListenerWrapperPtr(this), &_EventListenerWrapper::_OnHeightMapUpdated ) );
		}
	}

/*
=================================================
	OnEndProcessing
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_EventListenerWrapper::OnBeginProcessing ()
	{
		if ( not _genHeightMap )
			return;

		_InitHeightMap( _voxelTerrainSimulation->_computePass.GetQueue() );
	}

/*
=================================================
	OnEndProcessing
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_EventListenerWrapper::OnEndProcessing ()
	{
		if ( not _genHeightMap )
			return;

		_genHeightMap = false;

		const GLTexture2DPtr & tex = _glHeightMaps[ _currentTexture ];

		_voxelTerrainSimulation->_Push( MAIN_THREAD,
			FunctionBuilder::Create( _EventListenerWrapperPtr(this), &_EventListenerWrapper::_HeightMapUpdated, tex ) );

		++_currentTexture;
	}
	
/*
=================================================
	_OnHeightMapUpdated
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_EventListenerWrapper::_OnHeightMapUpdated ()
	{
		_genHeightMap = true;
	}

/*
=================================================
	Init
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_EventListenerWrapper::Init (uni_c_string options)
	{
		if ( _progBlockToHeight.IsNotNull() and _progInitHeight.IsNotNull() )
			return;

		CHECK( ShaderLoader::Load( _progBlockToHeight, "cl/voxel_terrain_to_height_map_1.cl", options ) );
		CHECK( _kernBlockToHeight.Create( _progBlockToHeight.ptr(), "Main" ) );

		CHECK( ShaderLoader::Load( _progInitHeight, "cl/init_height_map.cl", options ) );
		CHECK( _kernInitHeight.Create( _progInitHeight.ptr(), "Main" ) );
	}



//--------------------------------- _Thread ---------------------------------//
/*
=================================================
	destructor
=================================================
*/
	template <typename V, uint ID>
	inline VoxelTerrainSimulation<V,ID>::_Thread::~_Thread ()
	{
		Quit();
	}
	
/*
=================================================
	Run
=================================================
*/
	template <typename V, uint ID>
	inline bool VoxelTerrainSimulation<V,ID>::_Thread::Run ()
	{
		if ( not thread.IsValid() )
		{
			messageQueue.ClearAll();
			CHECK_RETURN( thread.Create( &_ThreadProc, this ) );
		}
		return true;
	}
	
/*
=================================================
	Quit
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_Thread::Quit ()
	{
		if ( thread.IsValid() )
		{
			looping = false;
			thread.Wait();
			thread.Delete();
		}
	}
	
/*
=================================================
	Push
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_Thread::Push (const _Message_t &msg)
	{
		messageQueue.Push( msg );
	}

/*
=================================================
	_ThreadProc
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_Thread::_ThreadProc (void *param)
	{
		((_Thread *) param)->_Loop();
	}
	
/*
=================================================
	_Loop
=================================================
*/
	template <typename V, uint ID>
	inline void VoxelTerrainSimulation<V,ID>::_Thread::_Loop ()
	{
		looping = true;

		while ( looping )
		{
			_MessageProcessor	processor;

			messageQueue.Flush();
			messageQueue.ProcessAll( processor );

			Thread::Yield();
		}
	}


}	// Voxels
